/*
 Copyright 2005-2008 Lanlan Song <jackysonglanlan@gmail.com>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package jacky.lanlan.song.extension.struts;

import java.beans.PropertyEditor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.log4j.Logger;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;
import org.apache.struts.util.MessageResources;

import org.springframework.context.ApplicationContext;

import jacky.lanlan.song.extension.struts.action.ActionContext;
import jacky.lanlan.song.extension.struts.action.ValueStack;
import jacky.lanlan.song.extension.struts.annotation.Domain;
import jacky.lanlan.song.extension.struts.annotation.Forward;
import jacky.lanlan.song.extension.struts.interceptor.ActionInterceptor;
import jacky.lanlan.song.extension.struts.util.ReflectionUtils;
import jacky.lanlan.song.extension.struts.util.bean.JavaBeanUtils;
import jacky.lanlan.song.extension.struts.util.bean.PropertyEditorManager;

/**
 * 使用POJO处理业务逻辑的Action控制器。
 * <p>
 * 控制器通过反射调用POJO的具体的执行方法，POJO式的 Action 需要标注 <code>POJOAction</code>。
 * 
 * @author Jacky.Song
 * @see jacky.lanlan.song.extension.struts.AnnotationConfigPlugIn
 * @see jacky.lanlan.song.extension.struts.annotation.POJOAction
 * @see jacky.lanlan.song.extension.struts.action.ValueStack
 * @see jacky.lanlan.song.extension.struts.action.ActionContext
 */
class POJOActionController extends Action {

	/**
	 * The message resources for this package.
	 */
	private static final MessageResources messages = MessageResources
			.getMessageResources("org.apache.struts.actions.LocalStrings");

	private static final Logger logger = Logger.getLogger(POJOActionController.class);

	// POJO Action 缓存 [Action 全类名 -> 对应的Action实例]
	private ConcurrentMap<String, Object> actionCache ;

	// POJO Action 执行方法缓存 [全类名+方法名 -> 对应的 Action 执行方法]
	private ConcurrentMap<String, Method> actionMethodCache ;

	// Spring
	private ApplicationContext applicationContext; 
	
	// 拦截器链缓存，只对POJOAction进行拦截
	private List<ActionInterceptor> interceptors_asc;// 按优先级升序排列
	private List<ActionInterceptor> interceptors_desc;// 按优先级降序排列
	
	//////////// 依赖项 /////////////
	
	public void setActionCache(ConcurrentMap<String, Object> actionCache) {
		this.actionCache = actionCache;
	}
	
	public void setActionMethodCache(ConcurrentMap<String, Method> actionMethodCache) {
		this.actionMethodCache = actionMethodCache;
	}
	
	public void setInterceptors(List<ActionInterceptor> interceptors) {
		Collections.sort(interceptors, ActionInterceptor.PRIORITY_ASC);
		this.interceptors_asc = interceptors;
		Collections.sort(interceptors, ActionInterceptor.PRIORITY_ASC);
		this.interceptors_desc = interceptors;
	}

	public void setApplicationContext(ApplicationContext context) {
		this.applicationContext = context;
	}
	
	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		// 这是标注了@POJOAction的parameter参数 格式:全类名!方法名
		String parameter = mapping.getParameter();

		// This may be null if the user hacks the query string.
		if (parameter == null) {
			String message = messages.getMessage("mapping.parameter", mapping.getPath());
			logger.error(message);
			throw new ServletException(message);
		}

		String[] params = mapping.getParameter().split(InfrastructureKeys.URL_SEPARATER);
		// parameter格式:全限定类名+PluginGlobals.URL_SEPARATER+方法名 ，所以[0]是类名，[1]是方法名

		//TODO 通过Spring得到Action
		//Object action = this.applicationContext.getBean(name);
		Object action = this.actionCache.get(params[0]);

		// 查找对应方法
		Method method = this.actionMethodCache.get(params[0]+params[1]);
		
		return this.executeBusiness(mapping, form, request, response, action, method);
	}

	//执行业务逻辑，返回执行后需要导向的页面Forward 
	private ActionForward executeBusiness(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response,
			Object pojoAction, Method execMethod) throws Exception {

		String distination =null;
		
		// 执行业务逻辑，接收目标页面(ActionForward)
		ActionForward forward = null;
		Exception execException=null;
		try {
			//提取请求数据
			Map<String, String> requestData = this.extractRequestData(request,form);
			
			//处理@Domain标注
			if(execMethod.isAnnotationPresent(Domain.class)) {
				Object domain=null;// 领域对象
				domain=this.assembleDomain(execMethod.getAnnotation(Domain.class).value(), requestData);
				request.setAttribute("domain", domain);// 在request里面保存领域对象
			}

			// 每次请求都构造一个VSACImpl，因为VSACImpl是一个轻量级对象，对于5.0+的VM来说，不存在性能问题
			VSACImpl vsi = new VSACImpl();
			vsi.setRequest(request);
			vsi.setResponse(response);
			vsi.setServlet(servlet);
			
			request.setAttribute(InfrastructureKeys.VSAC, vsi); //在request里面保留一个引用，以便拦截器可以使用
			request.setAttribute(InfrastructureKeys.EXECUTION, execMethod); //在request里面保留一个引用，以便拦截器可以使用
			request.setAttribute(InfrastructureKeys.REQ_DATA, requestData); //在request里面保留一个引用，以便拦截器可以使用
			
			/* 执行前调用拦截器(默认包括 [DownloadInterceptor] - [CancellInterceptor]) */
			// 升序排列，保证高优先级的preHandle()方法先调用
			for (ActionInterceptor interceptor : interceptors_asc) {
				distination = interceptor.preHandle(request, response, pojoAction);
				if(distination!=null) { // null代表终止处理过程，导向拦截器指定的路径
					if (distination.equals(Forward.INPUT)) {
						return mapping.getInputForward();
					}
					return mapping.findForward(distination);
				}
			}
			
			// 对 POJO Action 调用执行方法
			distination = (String) execMethod.invoke(
					pojoAction,
					vsi, // 隐式cast为ValueStack，然后传递给执行方法
					requestData);
			
			// 通过返回的String寻找ActionForward
			if (distination != null) {
				if (distination.equals(Forward.INPUT)) {// 如果是Input
					forward = mapping.getInputForward();// 返回InputForward
				}
				else {
					forward = mapping.findForward(distination);
				}
			}
			
			/* 执行后调用拦截器(默认包括CleanInterceptor) */
			// 降序排列，保证高优先级的postHandle()方法后调用
			for (ActionInterceptor interceptor : interceptors_desc) {
				interceptor.postHandle(request, response, pojoAction,forward);// TODO forward不可更改，那传进去有什么用
			}
		}
		catch (InvocationTargetException e) {
			// Rethrow the target exception if possible so that the
			// exception handling machinery can deal with it
			Throwable t = e.getTargetException();
			if (t instanceof Exception) { throw ((Exception) t); }
			String message = messages.getMessage("dispatch.error", mapping.getPath(),
					pojoAction.getClass() + execMethod.getName());
			logger.error(message, e);
			throw new ServletException(t);
		}
		catch (IllegalAccessException e) {
			String message = messages.getMessage("dispatch.error", mapping.getPath(),
					pojoAction.getClass() + execMethod.getName());
			logger.error(message, e);
			throw e;
		}
		catch(Exception e) {
			execException=e;
		}
		finally {
			/* 完成所有操作后调用拦截器 */
			// 降序排列，保证高优先级的afterCompletion()方法后调用
			for (ActionInterceptor interceptor : interceptors_desc) {
				interceptor.afterCompletion(request, response, pojoAction, execException);
			}
		}
		
		if(execException!=null) throw execException; //重新抛出异常
		
		// Return the returned ActionForward instance
		return (forward);
	}

	private final Lock extractDataLock = new ReentrantLock();

	// 解析出请求数据，[请求参数名 -> 字符串值]，如果form == null，则返回一个空Map
	@SuppressWarnings("unchecked")
	private Map<String, String> extractRequestData(HttpServletRequest request,ActionForm form) {
		if(form==null) {
			return Collections.EMPTY_MAP;
		}

		Map<String, String> map = null;
		if (DynaBean.class.isAssignableFrom(form.getClass())) {// LazyValidatorForm 实现了这个接口
			DynaBean db = (DynaBean) form;
			map = new HashMap<String, String>(8);// 默认储存8*0.75=6个参数，节省内存
			List<FormFile> uploadFiles=null;// 上载文件列表
			// 遍历每个属性
			for (DynaProperty dp : db.getDynaClass().getDynaProperties()) {
				Object value=db.get(dp.getName());
				if(FormFile.class.isAssignableFrom(value.getClass())) {// 如果是上载文件
					// uploadFiles lazy creation
					if(uploadFiles==null) {
						uploadFiles=new ArrayList<FormFile>(5);// 默认5个
					}
					uploadFiles.add((FormFile) value);
				}
				else {// 其他内容
					map.put(dp.getName(),value.toString());// 属性名不变，值转为String
				}
			}
			request.setAttribute(InfrastructureKeys.UPLOAD_DATA, uploadFiles);// 在请求中保存上载文件(供UploadInterceptor使用)
		}
		else {
			extractDataLock.lock();
			try {
				map = BeanUtils.describe(form);// 这个方法不知道是不是线程安全的，为保险起见，加锁
			}
			catch (Exception e) {
				logger.error("从Form [" + form + "] 解析请求数据时出错", e);
				throw new RuntimeException("从Form [" + form + "] 解析请求数据时出错", e);
			}
			finally {
				extractDataLock.unlock();
			}
		}
		return map;
	}
	
	//创建域对象，设置属性值，最后返回
	private <T> T assembleDomain(Class<T> domainClass,final Map<String, String> requestData) throws Exception {
		final T domain=domainClass.newInstance();
		
		JavaBeanUtils.doWithSetter(domainClass, new ReflectionUtils.MethodCallback() {
			public void doWith(Method setter) throws IllegalArgumentException, IllegalAccessException {
				String propName=JavaBeanUtils.setterToPropName(setter);//解析出属性名
				if(requestData.containsKey(propName)) {// 如果请求数据中存在同名的请求参数
					PropertyEditor pe=PropertyEditorManager.findEditor( // 查对应的PropertyEditor
							setter.getParameterTypes()[0]);
					if(pe!=null) {
						String value=requestData.get(propName);// 取字符串值
						pe.setAsText(value);// 设置字符串值到PropertyEditor，它负责类型转换
						
						try {
							setter.invoke(domain, pe.getValue());// 调用setter，传入转换后的值作参数
						}
						catch (InvocationTargetException e) {
							throw new RuntimeException("处理域对象，调用 ["+setter+"] 时出错",e);
						}
					}
				}
			}
		});
		return domain;
	}
	
	/**
	 * ValueStack和ActionContext的默认实现。
	 * <p>
	 * 同时实现 <code>ValueStack</code> 和<code>ActionContext</code>，这样，
	 * 当调用方需要访问底层的Servlet API时，可以强制cast为 <code>ActionContext</code>，
	 * 以获得底层 Servlet API 的访问权。
	 * </p>
	 * @author Jacky.Song
	 */
	class VSACImpl implements ValueStack, ActionContext {
		private HttpServletRequest request;

		private HttpServletResponse response;

		private HttpSession session;

		private ServletContext context;

		void setServlet(Servlet servlet) {
			this.context=servlet.getServletConfig().getServletContext();
		}
		
		void setRequest(HttpServletRequest request) {
			this.request = request;
			this.session = request.getSession(false);
		}

		void setResponse(HttpServletResponse response) {
			this.response = response;
		}

		/* ValueStack */

		public Object getAttribute(String name, Scope scope) {
			switch (scope) {
				case Request:
					return request.getAttribute(name);
				case Session:
					return session.getAttribute(name);
				case ServletContext:
					return context.getAttribute(name);
			}
			return null;
		}

		public Object getAttribute(String name) {
			Object value=null;
			for(Scope scope:Scope.values()) {//values是按Scope元素的声明顺序返回的数组，符合该方法的语意
				value=this.getAttribute(name, scope);
				if(value!=null) {
					return value;
				}
			}
			return value;
		}

		public void removeAttribute(String name, Scope scope) {
			//没有Ruby的“Duck Type”，switch无法抽到一个方法里面，该死的Java啊
			switch (scope) {
				case Request:
					request.removeAttribute(name);
					break;
				case Session:
					session.removeAttribute(name);
					break;
				case ServletContext:
					context.removeAttribute(name);
					break;
			}
		}

		public void setAttribute(String name, Object value, Scope scope) {
			switch (scope) {
				case Request:
					request.setAttribute(name,value);
					break;
				case Session:
					session.setAttribute(name,value);
					break;
				case ServletContext:
					context.setAttribute(name,value);
					break;
			}
		}

		public void setAttribute(String name, Object value) {
			request.setAttribute(name, value);
		}
		
		public String getRequestParameter(String name) {
			return request.getParameter(name);
		}

		/* ActionContext */

		public ServletContext getContext() {
			return this.context;
		}

		public HttpSession getSession() {
			return this.session;
		}

		public HttpServletRequest getRequest() {
			return this.request;
		}

		public HttpServletResponse getResponse() {
			return this.response;
		}

	}
	
}
